using System;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;

namespace SharpWind.Scene.Cameras
{
	public abstract class Camera
	{
		protected SceneNode sceneNode = null;
	
		protected Vector3 position	= Vector3.Empty;
		protected Vector3 right		= new Vector3( 1, 0, 0 );
		protected Vector3 up		= new Vector3( 0, 1, 0 );
		protected Vector3 look		= new Vector3( 0, 0, 1 );
		
		protected Matrix viewMatrix			= Matrix.Identity;
		protected Matrix projectionMatrix	= Matrix.Identity;
		
		protected Viewport viewport;
		
		protected float nearClipPlane = 1f;
		protected float farClipPlane  = 100f;
		
		protected float fov = 60f;

		protected bool viewIsDirty = false;
		protected bool projectionIsDirty = false;
		
		public Camera()
		{
			this.viewport = new Viewport();
			SetViewport( 0, 0, Engine.Instance.Settings.Width,
						 Engine.Instance.Settings.Height, 1f, 100f );
		}
		
		public void AttachToObject( SceneNode sceneNode )
		{
			this.sceneNode = sceneNode;
		}
		
		public void DetachFromObject()
		{
			this.sceneNode = null;
		}
		
		public void SetViewport( int x, int y, int width, int height, 
								 float nearPlane, float farPlane )
		{
			this.viewport		 = new Viewport();
			this.viewport.X		 = x;
			this.viewport.Y		 = y;
			this.viewport.Width  = width;
			this.viewport.Height = height;
			this.viewport.MinZ	 = 0f;
			this.viewport.MaxZ	 = 1f;

			this.nearClipPlane = nearPlane;
			this.farClipPlane  = farPlane;
			
			Engine.Instance.Renderer.Device.Viewport = Viewport;
			
			this.projectionIsDirty = true;
		}
		
		public void Activate()
		{
			Engine.Instance.Scene.ActiveCamera = this;
		}
		
		public virtual void Move( Vector3 offset )
		{
			this.position += offset;
			this.viewIsDirty = true;
		}
		
		public virtual void Update()
		{
		}
		
		public virtual void Rotate( float x, float y, float z )
		{
		}
		
		public Vector3 Position
		{
			get
			{
				return this.position;
			}
			set
			{
				if( this.position != value )
				{
					this.position = value;
					this.viewIsDirty = true;
				}
			}
		}
		
		public Vector3 Look
		{
			get
			{
				return this.look;
			}
			set
			{
				if( this.look != value )
				{
					this.look = value;
					this.viewIsDirty = true;
				}
			}
		}
		
		public Vector3 Up
		{
			get
			{
				return this.up;
			}
			set
			{
				if( this.up != value )
				{
					this.up = value;
					this.viewIsDirty = true;
				}
			}
		}
		
		public Vector3 Right
		{
			get
			{
				return this.right;
			}
			set
			{
				if( this.right != value )
				{
					this.right = value;
					this.viewIsDirty = true;
				}
			}
		}
		
		public float NearClipPlane
		{
			get
			{
				return this.nearClipPlane;
			}
			set
			{
				if( this.nearClipPlane != value )
				{
					this.nearClipPlane = value;
					this.projectionIsDirty = true;
				}
			}
		}
		
		public float FarClipPlane
		{
			get
			{
				return this.farClipPlane;
			}
			set
			{
				if( this.farClipPlane != value )
				{
					this.farClipPlane = value;
					this.projectionIsDirty = true;
				}
			}
		}
		
		public float FieldOfView
		{
			get
			{
				return this.fov;
			}
			set
			{
				if( this.fov != value )
				{
					this.fov = value;
					this.projectionIsDirty = true;
				}
			}
		}
		
		internal Matrix ViewMatrix
		{
			get
			{
				if( this.viewIsDirty )
				{
					this.look.Normalize();
					
					this.right = Vector3.Cross( this.up, this.look );
					this.right.Normalize();
				
					this.up = Vector3.Cross( this.look, this.right );
					this.up.Normalize();
					
					this.viewMatrix.M11 = right.X; this.viewMatrix.M12 = this.up.X; this.viewMatrix.M13 = this.look.X; this.viewMatrix.M14 = 0f;
					this.viewMatrix.M21 = right.Y; this.viewMatrix.M22 = this.up.Y; this.viewMatrix.M23 = this.look.Y; this.viewMatrix.M24 = 0f;
					this.viewMatrix.M31 = right.Z; this.viewMatrix.M32 = this.up.Z; this.viewMatrix.M33 = this.look.Z; this.viewMatrix.M34 = 0f;
					this.viewMatrix.M41 = -Vector3.Dot( this.position, this.right );
					this.viewMatrix.M42 = -Vector3.Dot( this.position, this.up );
					this.viewMatrix.M43 = -Vector3.Dot( this.position, this.look );
					this.viewMatrix.M44 = 1f;
					
					this.viewIsDirty = false;
				}
				
				return this.viewMatrix;
			}
		}
		
		internal Matrix ProjectionMatrix
		{
			get
			{
				if( this.projectionIsDirty )
				{
					float aspectRatio = (float)this.viewport.Width / (float)this.viewport.Height;
					this.projectionMatrix = Matrix.PerspectiveFovLH( Geometry.DegreeToRadian( this.fov ), aspectRatio,
																	 this.nearClipPlane,
																	 this.farClipPlane );
					this.projectionIsDirty = false;
				}
				
				return this.projectionMatrix;
			}
		}
		
		internal Viewport Viewport
		{
			get
			{
				return this.viewport;
			}
		}
	}
}
